-
Notifications
You must be signed in to change notification settings - Fork 0
feat(firebase): Add Firebase configuration detection and validation #5
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
- Detect Firebase credential files (google-services.json, GoogleService-Info.plist) - Support XML and JSON plist formats for iOS configuration - Validate credentials against Zod schemas - Interactive wizard for Firebase configuration management - OAuth 2.0 with PKCE support for downloading configs from Firebase Console - Support React Native, Flutter, iOS, and Android platforms Key features: - Automatic detection of platform and expected credential file locations - Comprehensive validation with detailed error messages - Project ID verification across platforms - Integration with existing Clix CLI command system Co-Authored-By: Claude <noreply@anthropic.com>
|
Note Other AI code review bot(s) detectedCodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review. WalkthroughAdds a new /firebase slash command plus an interactive Ink-based Firebase wizard and a complete Firebase feature set: OAuth PKCE auth, Management API client, token storage, credential detection/validation, downloader, Zod schemas/validators, error types, UI, and docs. Changes
Suggested reviewers
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a0c56059c0
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 9
🤖 Fix all issues with AI agents
In `@llms.txt`:
- Around line 229-237: The documentation added a new system command `/firebase`,
increasing the system commands from 9 to 10, so update the earlier slash-command
totals to read "3 autonomous + 5 skills + 10 system = 18 total" (or update any
"total slash-commands" summary) to avoid inconsistency; locate the earlier
totals/summary text that lists the counts (look for phrases like "autonomous",
"skills", "system", or "total slash-commands") and change the numbers and the
computed total to 18 wherever they appear.
In `@src/lib/services/firebase/detector.ts`:
- Around line 327-382: The detector currently treats any file flagged as
"inExpectedLocation" by findGoogleServicesJson but that helper marks expected
locations globally; update detectAndroidCredential to prefer and label files
using the platform-specific expectedPaths: when selecting expectedFile, find a
file whose path matches any entry in expectedPaths (compare normalized relative
paths or compare absolutePath to path.join(projectPath, expectedPath)), set
inExpectedLocation based on that match, and set expectedPath only when the
chosen file is not inExpectedLocation (use the first matching expectedPaths
value if applicable); apply the same change to the iOS detector
(detectIosCredential / findGoogleServiceInfoPlist) so validation and guidance
use the platform-specific expectedPaths.
- Around line 149-176: The function detectPlatform currently returns
'react-native' when detectNativePlatforms finds both hasIos and hasAndroid even
if isReactNativeProject/isFlutterProject were false; change the logic in
detectPlatform so that when hasIos && hasAndroid and neither
isReactNativeProject(projectPath) nor isFlutterProject(projectPath) are true, it
returns 'unknown' instead of 'react-native' to avoid misclassifying purely
native monorepos; update the hasIos && hasAndroid branch in detectPlatform (and
any related comments) to implement this conditional return and add/update tests
that assert dual-native-but-not-cross-platform repos yield 'unknown'.
In `@src/lib/services/firebase/oauth/auth-client.ts`:
- Around line 96-167: The waitForCallback Promise currently starts a server
bound to all interfaces and never clears the timeout; change server.listen(...)
to bind explicitly to '127.0.0.1' using GOOGLE_OAUTH_CONFIG.callbackPort, store
the setTimeout return value in a variable (e.g., timeoutHandle), and add a small
cleanup helper (e.g., cleanup = () => { clearTimeout(timeoutHandle); try {
server.close(); } catch {} }) that you call before every resolve and reject
(inside the OAuth success branch where resolve({ code, state }) is called, in
the error and missing-code branches before reject, in the server.on('error')
handler, and in the timeout callback) so the server is closed and the timeout
cleared on all code paths.
- Around line 34-89: generate and store a cryptographically-random OAuth state
string when building the URL in generateAuthUrl() (add a private this.state:
string | null), include that state in the client.generateAuthUrl(...)
parameters, and in waitForCallback() validate that the incoming state equals the
stored this.state before accepting the code; if missing or mismatched,
throw/reject and clear the stored state. Ensure state is cleared after
successful or failed validation to avoid reuse.
In `@src/lib/services/firebase/oauth/token-store.ts`:
- Around line 45-49: The save method writes OAuth tokens with default permissive
filesystem bits; change mkdir and file writes to enforce restrictive
permissions: create the directory with mode 0o700 (use fs.mkdir(..., {
recursive: true, mode: 0o700 })) and write the token file with mode 0o600 (use
fs.writeFile with the mode option or follow up with fs.chmod(this.tokenPath,
0o600)). Update the save function (in token-store.ts, method save) to ensure the
directory and file permissions are set atomically/explicitly so the token JSON
is only readable by the owner.
In `@src/lib/services/firebase/schemas.ts`:
- Around line 12-17: The AndroidClientInfoSchema currently allows uppercase
letters due to the case-insensitive regex flag; update the schema's regex in
AndroidClientInfoSchema by removing the `i` flag so the pattern becomes strictly
lowercase (use `/^[a-z][a-z0-9_]*(\.[a-z][a-z0-9_]*)+$/`) to enforce lowercase
package names while keeping the existing min(1) and error message intact.
In `@src/ui/components/FirebaseWizard.tsx`:
- Around line 828-833: In handleAction (the async callback handling
CredentialAction), await the async call to handleDownload so the promise is
awaited and potential rejections are propagated/handled; update the 'download'
case to use await handleDownload() (keeping the surrounding async handleAction)
and ensure any needed try/catch around the switch remains or is added to surface
errors from handleDownload.
- Around line 149-162: The openBrowser function is vulnerable to shell injection
and mishandles Windows arguments while ignoring exec errors; replace the
string-interpolated exec call with safe child_process APIs (e.g., spawn or
execFile) and pass the URL as an argument array instead of interpolating into a
shell command, handle Windows by invoking cmd /c start with an explicit empty
title (e.g., ['cmd', ['/c','start','', url]] or equivalent) or call
spawn('start', ['', url], {shell:true}) carefully, and attach an error
callback/listener to surface failures (or log/throw the error) so exec/spawn
errors are not silently ignored; update references in openBrowser and any call
sites accordingly.
🧹 Nitpick comments (13)
src/lib/services/firebase/oauth/types.ts (1)
12-18: Redundant field declarations in extended interface.
OAuthTokensextendsCredentialsfromgoogle-auth-library, which already declaresaccess_token,refresh_token,token_type, andexpiry_date. Re-declaring them here is redundant but documents the expected shape explicitly. Consider removing the duplicates or adding a comment explaining the intent.src/lib/services/firebase/api/types.ts (1)
31-35: Consider adding'STATE_UNSPECIFIED'to project state union.The Firebase Management API can return
STATE_UNSPECIFIEDin addition toACTIVEandDELETED. Adding it would make the type more robust against unexpected API responses.Suggested change
/** * Project state. */ - state: 'ACTIVE' | 'DELETED'; + state: 'STATE_UNSPECIFIED' | 'ACTIVE' | 'DELETED';src/ui/components/FirebaseStatusDisplay.tsx (3)
99-109: Potential non-unique React key if errors have duplicate path+message.The key
${error.path}-${error.message}could collide if the same error appears multiple times. Consider appending the index for guaranteed uniqueness.Suggested fix
- {credential.errors.slice(0, 3).map((error) => ( - <Text key={`${error.path}-${error.message}`} color="red"> + {credential.errors.slice(0, 3).map((error, index) => ( + <Text key={`${error.path}-${error.message}-${index}`} color="red">
154-166: Consider extracting platform-needs helpers to a shared utility.
platformNeedsAndroidandplatformNeedsIosduplicate logic fromdownloader.ts(lines 179-181). Extract to a shared utility in the firebase module to maintain consistency.
180-187:getStatusColorreturns 'red' for both missing and n/a statuses.Currently
n/aand✗both render as red. Consider using a neutral color (e.g.,dimColororgray) forn/ato differentiate "not applicable" from "missing/error".Suggested fix
function getStatusColor(status: string): string { if (status === '✓') return 'green'; if (status === '!') return 'yellow'; + if (status === 'n/a') return 'gray'; return 'red'; }src/lib/services/firebase/downloader.ts (1)
145-168: Consider adding error handling for file write operations.
downloadAndroidConfiganddownloadIosConfigdon't catch errors fromfs.mkdirorfs.writeFile. If the directory creation or file write fails (e.g., permission denied), the error propagates uncaught. Consider wrapping in try-catch with meaningful error messages.src/ui/components/FirebaseWizard.tsx (1)
656-706: Consider simplifying callback dependency management.The ref-based pattern to avoid circular dependencies works but adds complexity. Consider extracting the download flow into a custom hook or using a reducer pattern for cleaner state management.
src/lib/services/firebase/firebase-service.ts (1)
64-71: Consider extracting platform requirements logic to a shared utility.The
needsAndroid/needsIoscomputation is duplicated across multiple files (here, inFirebaseWizard.tsx, and indetector.ts). Extract this to a shared helper function to ensure consistency.♻️ Example shared utility
// In types.ts or a new utils.ts export function getPlatformRequirements(platform: Platform): { needsAndroid: boolean; needsIos: boolean } { const needsAndroid = platform === 'android' || platform === 'react-native' || platform === 'flutter'; const needsIos = platform === 'ios' || platform === 'react-native' || platform === 'flutter'; return { needsAndroid, needsIos }; }src/lib/services/firebase/api/firebase-api.ts (2)
39-56: Consider adding request timeout.The
fetchcalls have no timeout configuration. Long-running or hung requests could block the CLI indefinitely. Consider usingAbortControllerwith a timeout.♻️ Example with timeout
private async request<T>(path: string): Promise<T> { const token = await this.getAccessToken(); const url = `${BASE_URL}${path}`; + const controller = new AbortController(); + const timeoutId = setTimeout(() => controller.abort(), 30000); - const response = await fetch(url, { - headers: { - Authorization: `Bearer ${token}`, - 'Content-Type': 'application/json', - }, - }); + try { + const response = await fetch(url, { + headers: { + Authorization: `Bearer ${token}`, + 'Content-Type': 'application/json', + }, + signal: controller.signal, + }); if (!response.ok) { const error = await response.text(); throw new Error(`Firebase API error (${response.status}): ${error}`); } return response.json() as Promise<T>; + } finally { + clearTimeout(timeoutId); + } }
103-106: Consider URL-encoding path parameters defensively.While Firebase project IDs and app IDs typically don't contain special characters, encoding them with
encodeURIComponentwould be a defensive measure against edge cases.src/lib/services/firebase/types.ts (2)
86-90: Naming conflict with existingValidationErrorclass.This
ValidationErrorinterface shadows theValidationErrorclass insrc/lib/errors/types.ts. Consider renaming toFirebaseValidationErrororCredentialValidationErrorto avoid confusion when both are imported.
104-125: Consider documenting thevalid/contentinvariant.The
contentfield is optional, but whenvalidis true,contentshould always be defined. This invariant is relied upon infirebase-service.ts. Consider adding a JSDoc comment to document this relationship.📝 Documentation suggestion
export interface FirebaseCredentialFile { /** File path relative to project root */ path: string; /** Absolute file path */ absolutePath: string; /** Target platform */ platform: 'android' | 'ios'; /** Credential file type */ type: CredentialFileType; /** Whether file exists */ exists: boolean; /** Whether file is valid */ valid: boolean; /** Validation errors if any */ errors: ValidationError[]; - /** Parsed content if valid */ + /** Parsed content if valid. Guaranteed to be defined when `valid` is true. */ content?: GoogleServicesJson | GoogleServiceInfoPlist; /** Whether file is in expected location */ inExpectedLocation: boolean; /** Expected location if different from actual */ expectedPath?: string; }src/lib/services/firebase/detector.ts (1)
607-623: Reuse the existingneedsAndroidConfig/needsIosConfighelpers.Avoids duplicating platform logic and keeps behavior consistent.
♻️ Suggested refactor
- const needsAndroid = - platform === 'android' || platform === 'react-native' || platform === 'flutter'; - const needsIos = platform === 'ios' || platform === 'react-native' || platform === 'flutter'; + const needsAndroid = needsAndroidConfig(platform); + const needsIos = needsIosConfig(platform);
- Add CSRF protection via OAuth state parameter validation - Bind callback server to localhost only for security - Add proper timeout cleanup in OAuth flow - Harden token file permissions (0o600 for file, 0o700 for dir) - Use spawn instead of exec for shell injection prevention - Add missing await for handleDownload - Return 'unknown' for dual-platform detection - Expand iOS plist search to include root-level directories - Use platform-specific expectedPaths for credential detection - Enforce lowercase for Android package names in schema - Update llms.txt slash-command count (17→18) Co-Authored-By: Claude <noreply@anthropic.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@src/lib/services/firebase/detector.ts`:
- Around line 632-648: detectFirebaseConfig currently treats an 'unknown'
platform as not needing Android or iOS, causing configured to be true when both
configs are missing; update detectFirebaseConfig to call
detectNativePlatforms(projectPath) when detectPlatform returns 'unknown' and use
its result to set needsAndroid and needsIos (instead of defaulting to false), so
the boolean flags reflect actual native platform presence before computing
androidConfigured, iosConfigured and configured; reference detectFirebaseConfig,
detectPlatform, detectNativePlatforms, needsAndroid, needsIos and
getExpectedPaths when making the change.
♻️ Duplicate comments (2)
src/lib/services/firebase/oauth/token-store.ts (1)
45-53: Ensure existing token files are re-permissioned too.
fs.writeFile(..., { mode })only sets permissions on newly created files; if a token file already exists from an older version, it can keep permissive bits. Considerchmodafter write (and optionally on the directory) to guarantee owner-only access on upgrades.🔐 Suggested hardening
await fs.mkdir(dir, { recursive: true, mode: 0o700 }); // Write token file with restricted permissions (owner read/write only) await fs.writeFile(this.tokenPath, JSON.stringify(tokens, null, 2), { encoding: 'utf-8', mode: 0o600, }); + // Ensure existing paths are tightened as well + await fs.chmod(dir, 0o700); + await fs.chmod(this.tokenPath, 0o600);src/ui/components/FirebaseWizard.tsx (1)
150-169: Handlespawnerrors to avoid CLI crashes when browser open fails.If
open/xdg-open/cmdisn’t available,spawnemits anerrorevent; without a handler, Node can throw. Add an error listener (and ideally surface a user-facing message).🛠 Minimal safeguard
- spawn(command, args, { detached: true, stdio: 'ignore' }).unref(); + const child = spawn(command, args, { detached: true, stdio: 'ignore' }); + child.on('error', (err) => { + // eslint-disable-next-line no-console + console.error('Failed to open browser:', err.message); + }); + child.unref();
- Merge main branch containing ios-setup feature - Resolve conflicts in package.json (keep both expo libs and google-auth-library) - Resolve conflicts in llms.txt (update counts: 19 slash commands = 4 autonomous + 5 interactive + 10 system) - Regenerate bun.lock with all dependencies Co-Authored-By: Claude <noreply@anthropic.com>
- Add 'firebase' overlay type to useOverlays - Add showFirebaseWizard, handleFirebaseComplete, handleFirebaseCancel handlers - Add 'firebase' case to useCommandHandler switch statement - Render FirebaseWizard component when firebase overlay is active - Fixes "Command /firebase is not implemented yet" error Co-Authored-By: Claude <noreply@anthropic.com>
…-cli into feat/firebase-setup
Fixes duplicate 'local-ios-setup' key issue caused by merge. Co-Authored-By: Claude <noreply@anthropic.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
🤖 Fix all issues with AI agents
In `@src/lib/embedded-skills.ts`:
- Around line 1555-1870: The EMBEDDED_SKILLS object contains two entries with
the same key 'local-ios-setup', causing the later one to silently overwrite the
earlier; locate the duplicate 'local-ios-setup' entries in EMBEDDED_SKILLS and
either remove the redundant copy or merge their contents into a single
consolidated 'local-ios-setup' value (ensuring no required text is lost), then
run tests/lint to confirm no other duplicate keys remain.
- Around line 647-842: The repo docs are missing the newly added skill
'skill-creator' (clix-skill-creator) declared in embedded-skills.ts; update
README.md and llms.txt to include an entry for "clix-skill-creator" (display
name: Skill Creator, short description: Generate new Clix agent skills) in the
same format/section as other skills, ensuring the trigger phrase
`clix-skill-creator` or `skill-creator` is listed in llms.txt and a brief README
entry mirrors the SKILL.md frontmatter (name/display-name/short-description) so
documentation stays in sync with the embedded-skills.ts addition.
🧹 Nitpick comments (1)
llms.txt (1)
586-617: Document the OAuth download functionality.The documentation describes detection and validation features well, but it doesn't mention a key capability from the PR objectives: the ability to download missing Firebase configurations from the Firebase Console using OAuth 2.0 with PKCE. The example shows "Press Enter to configure" but doesn't explain what this action does.
📝 Suggested enhancement to document OAuth download feature
Consider adding a bullet point under Features: to mention the download capability:
**Features:** - Auto-detects Firebase credential files in project - Validates JSON/plist structure against Firebase schema - Reports issues (missing files, invalid format, wrong location) - Provides recommendations with help URLs +- Downloads missing configs from Firebase Console via OAuth 2.0 - Supports React Native, Flutter, iOS, and Android projectsAnd clarify the interactive action in the example section:
-Press Enter to configure, Esc to skip +Press Enter to download missing configs (opens browser for Firebase OAuth), Esc to skipBased on PR objectives, the OAuth download feature is a significant capability that users should be aware of.
… true When platform was 'unknown', the previous logic resulted in: - needsAndroid = false - needsIos = false - androidConfigured = !false = true - iosConfigured = !false = true - configured = true (incorrect!) Now for unknown platform, configured is true only if at least one valid config file (Android or iOS) is found. Co-Authored-By: Claude <noreply@anthropic.com>
skill-creator is an internal developer tool for creating new skills, not intended for end users. Exclude it from the embedded skills to keep the command list focused on user-facing features. Co-Authored-By: Claude <noreply@anthropic.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@src/lib/services/firebase/detector.ts`:
- Around line 304-335: The Windows path separator causes stored relative plist
paths to use backslashes and mismatch expectedPaths (which use forward slashes);
update both places that compute plistPath in the iOS directory loop and the
root-level native project loop so the relative path is normalized to POSIX style
before any comparisons or pushing into results (e.g., compute plistPath with
path.posix.join or call .replace(/\\+/g, '/')), ensure comparisons against
expectedPaths and the results.some((r) => r.path === plistPath) use the
normalized path, and keep the use of fullPath for fileExists/unix filesystem
checks unchanged.
♻️ Duplicate comments (1)
src/lib/services/firebase/detector.ts (1)
632-655:unknownplatform can still mark dual-native as configured with only one config (and suppress issues).When
detectPlatform()returns'unknown'for dual-native repos,configuredbecomestrueif either config is valid. That contradicts “fully configured” semantics and hides missing-config issues. Consider derivingneedsAndroid/needsIosfromdetectNativePlatforms()when platform is'unknown', and use those flags for both issue generation andconfigured.🛠️ Proposed fix
export async function detectFirebaseConfig(projectPath: string): Promise<FirebaseDetectionResult> { const platform = await detectPlatform(projectPath); + const native = + platform === 'unknown' ? await detectNativePlatforms(projectPath) : { hasIos: false, hasAndroid: false }; const expectedPaths = getExpectedPaths(platform, projectPath); const android = await detectAndroidCredential(projectPath, expectedPaths.android); const ios = await detectIosCredential(projectPath, expectedPaths.ios); - const issues = generateIssues(android, ios, platform, expectedPaths); + const needsAndroid = + needsAndroidConfig(platform) || (platform === 'unknown' && native.hasAndroid); + const needsIos = + needsIosConfig(platform) || (platform === 'unknown' && native.hasIos); + const issues = + platform === 'unknown' + ? [ + ...(needsAndroid ? generateAndroidIssues(android, expectedPaths.android[0]) : []), + ...(needsIos ? generateIosIssues(ios, expectedPaths.ios[0]) : []), + ] + : generateIssues(android, ios, platform, expectedPaths); // Determine if Firebase is configured - const needsAndroid = - platform === 'android' || platform === 'react-native' || platform === 'flutter'; - const needsIos = platform === 'ios' || platform === 'react-native' || platform === 'flutter'; - let configured: boolean; if (platform === 'unknown') { - // For unknown platform, configured is true only if at least one valid config exists - configured = (android?.valid ?? false) || (ios?.valid ?? false); + const androidConfigured = !needsAndroid || (android?.valid ?? false); + const iosConfigured = !needsIos || (ios?.valid ?? false); + configured = androidConfigured && iosConfigured; } else { const androidConfigured = !needsAndroid || (android?.valid ?? false); const iosConfigured = !needsIos || (ios?.valid ?? false); configured = androidConfigured && iosConfigured; }
Use POSIX path separators for relative plist paths stored in results array to ensure consistent forward slashes across platforms. This fixes false "misplaced" warnings on Windows where path.join produces backslashes that don't match expectedPaths. Co-Authored-By: Claude <noreply@anthropic.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@src/lib/services/firebase/detector.ts`:
- Around line 67-75: The isReactNativeProject function can throw or produce
false negatives because it spreads pkg.dependencies and pkg.devDependencies
which may be undefined; update the merge to guard with defaults (e.g., use {}
fallback for pkg.dependencies and pkg.devDependencies or use optional chaining)
so deps is always an object, then check deps['react-native'] || deps.expo as
before; ensure you update the code paths in isReactNativeProject to handle
missing package.json fields without throwing.
♻️ Duplicate comments (1)
src/lib/services/firebase/detector.ts (1)
634-667:unknownplatform can mask missing configs in dual-native repos.When
detectPlatformreturnsunknownfor dual-native projects,configuredbecomes true if either config is valid and issues stay empty (becauseneedsAndroidConfig/needsIosConfigignoreunknown). This can hide a missing iOS or Android config. Consider usingdetectNativePlatformsto determine which platforms exist and generate issues/configured accordingly.🛠️ Suggested fix (keeps current behavior for truly unknown projects)
export async function detectFirebaseConfig(projectPath: string): Promise<FirebaseDetectionResult> { const platform = await detectPlatform(projectPath); const expectedPaths = getExpectedPaths(platform, projectPath); const android = await detectAndroidCredential(projectPath, expectedPaths.android); const ios = await detectIosCredential(projectPath, expectedPaths.ios); - const issues = generateIssues(android, ios, platform, expectedPaths); + let issues = generateIssues(android, ios, platform, expectedPaths); // Determine if Firebase is configured - // For unknown platform, check if at least one valid config file exists - const needsAndroid = - platform === 'android' || platform === 'react-native' || platform === 'flutter'; - const needsIos = platform === 'ios' || platform === 'react-native' || platform === 'flutter'; + let needsAndroid = + platform === 'android' || platform === 'react-native' || platform === 'flutter'; + let needsIos = platform === 'ios' || platform === 'react-native' || platform === 'flutter'; let configured: boolean; if (platform === 'unknown') { - // For unknown platform, configured is true only if at least one valid config exists - configured = (android?.valid ?? false) || (ios?.valid ?? false); + const native = await detectNativePlatforms(projectPath); + if (native.hasAndroid || native.hasIos) { + needsAndroid = native.hasAndroid; + needsIos = native.hasIos; + issues = []; + if (needsAndroid) { + issues.push(...generateAndroidIssues(android, expectedPaths.android[0])); + } + if (needsIos) { + issues.push(...generateIosIssues(ios, expectedPaths.ios[0])); + } + const androidConfigured = !needsAndroid || (android?.valid ?? false); + const iosConfigured = !needsIos || (ios?.valid ?? false); + configured = androidConfigured && iosConfigured; + } else { + configured = (android?.valid ?? false) || (ios?.valid ?? false); + } } else { const androidConfigured = !needsAndroid || (android?.valid ?? false); const iosConfigured = !needsIos || (ios?.valid ?? false); configured = androidConfigured && iosConfigured; }
🧹 Nitpick comments (1)
src/lib/services/firebase/detector.ts (1)
184-210: Expand iOS expected paths for RN/native layouts.For
react-nativeandios,expectedPaths.iosonly includesios/GoogleService-Info.plistor root. Common layouts place the plist underios/<App>/or<App>/(sibling to.xcodeproj), which will be flagged as “misplaced” and produce misleading recommendations. Consider deriving iOS expected paths from detected app directories (e.g., scan for.xcodeproj/.xcworkspacesiblings or reuse the app-specific plist discovery results) and include those paths inexpectedPaths.ios.
| async function isReactNativeProject(projectPath: string): Promise<boolean> { | ||
| try { | ||
| const packageJson = await fs.readFile(path.join(projectPath, 'package.json'), 'utf-8'); | ||
| const pkg = JSON.parse(packageJson); | ||
| const deps = { ...pkg.dependencies, ...pkg.devDependencies }; | ||
| return Boolean(deps['react-native'] || deps.expo); | ||
| } catch { | ||
| return false; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Guard against undefined dependency maps.
Object spread on pkg.dependencies/pkg.devDependencies can throw if either is undefined, which turns RN detection into a false negative.
🛠️ Proposed fix
- const deps = { ...pkg.dependencies, ...pkg.devDependencies };
+ const deps = { ...(pkg.dependencies ?? {}), ...(pkg.devDependencies ?? {}) };📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| async function isReactNativeProject(projectPath: string): Promise<boolean> { | |
| try { | |
| const packageJson = await fs.readFile(path.join(projectPath, 'package.json'), 'utf-8'); | |
| const pkg = JSON.parse(packageJson); | |
| const deps = { ...pkg.dependencies, ...pkg.devDependencies }; | |
| return Boolean(deps['react-native'] || deps.expo); | |
| } catch { | |
| return false; | |
| } | |
| async function isReactNativeProject(projectPath: string): Promise<boolean> { | |
| try { | |
| const packageJson = await fs.readFile(path.join(projectPath, 'package.json'), 'utf-8'); | |
| const pkg = JSON.parse(packageJson); | |
| const deps = { ...(pkg.dependencies ?? {}), ...(pkg.devDependencies ?? {}) }; | |
| return Boolean(deps['react-native'] || deps.expo); | |
| } catch { | |
| return false; | |
| } |
🤖 Prompt for AI Agents
In `@src/lib/services/firebase/detector.ts` around lines 67 - 75, The
isReactNativeProject function can throw or produce false negatives because it
spreads pkg.dependencies and pkg.devDependencies which may be undefined; update
the merge to guard with defaults (e.g., use {} fallback for pkg.dependencies and
pkg.devDependencies or use optional chaining) so deps is always an object, then
check deps['react-native'] || deps.expo as before; ensure you update the code
paths in isReactNativeProject to handle missing package.json fields without
throwing.
Summary
Add comprehensive Firebase configuration detection and validation system. Users can now automatically detect Firebase credential files (google-services.json for Android, GoogleService-Info.plist for iOS), validate them against Firebase schemas, and optionally download configs directly from Firebase Console using OAuth 2.0 authentication.
Details
Core Components:
detector.ts: Automatically detects Firebase credential files and their locationsvalidator.ts: Validates credentials against Zod schemas with detailed error messagesfirebase-service.ts: Main service orchestrating detection, validation, and recommendationsFirebaseWizard.tsx: Interactive UI for configuration managementKey Features:
/firebasecommand for interactive configuration workflowHow to Validate
bun run devto start interactive mode/firebaseto open the Firebase configuration wizardPre-Merge Checklist
Code Quality
bun run build)bun run typecheck)bun run lint)bun test- 529 tests)Documentation
/firebasecommanddoctorandinstallCommit Standards
🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Documentation
✏️ Tip: You can customize this high-level summary in your review settings.